package com.zx.JDK特性.jdk1_8;

import com.zx.common.entity.User;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.DoubleSummaryStatistics;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Test;

/**
 * 流是数据渠道，用于操作数据源（集合、数组等）所生成的元素序列。
 * ①Stream自己不会存储元素。
 * ②Stream不会改变源对象。相反，会返回持有新结果的新Stream。
 * ③Stream操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
 */
public class Stream1 {

    List<User> list = Arrays.asList(
            new User(2L, "AAA", 20),
            new User(4L, "BBB", 40),
            new User(6L, "CCC", 40),
            new User(5L, "DDD", 50),
            new User(3L, "EEE", 30),
            new User(1L, "FFF", 10)
    );

    //操作的三个步骤： 1、创建一个流Stream 2、中间操作 3、终止操作

    /**
     * 创建流
     */
    @Test
    public static void test01() {
        //1. 可以通过Collections系列集合提供的stream()或parallelStream()
        final List<String> list = new ArrayList<>();
        final Stream<String> stream = list.stream();

        //2. 通过Arrays中的静态方法stream()获取数组流
        final User[] li = new User[10];
        final Stream<User> stream1 = Arrays.stream(li);

        //3. 通过Stream类中的静态方法of()
        final Stream<String> stream3 = Stream.of("1", "2", "3");

        //4. 创建无限流
        //迭代
        Stream.iterate(1, x -> x + 2).limit(10).forEach(System.out::println);

        //生成
        final Stream<Double> generate = Stream.generate(() -> Math.random());
        generate.limit(10).forEach(System.out::println);
    }

    /**
     * 中间操作：API
     * 多个中间操作可以连接起来形成一个流水线，除非流水想上出发终止操作。否则中间操作不会执行任何的处理，而在终止操作时一次性全部处理，成为“惰性求值”。
     * 1.筛选和切片
     */
    @Test
    public void test02() {
        //1. 创建流
        final Stream<User> stream = this.list.stream();

        stream
                .filter(user -> user.getAge() > 10)//过滤符合条件的流元素
                .limit(5)//只取5个
                .skip(2)//跳过前2个
                .distinct()//去重，需重写hashcode和equals方法
                .forEach(System.out::println);
    }

    /**
     * 中间操作：API
     * 接受Lambda，将元素转换成其他形式或提取信息，接受一个函数作为参数， 该函数会被应用到每个元素上，并将其映射成一个新的元素。
     * 2.映射
     */
    @Test
    public void test03() {
        this.list.stream()
                .map(User::getName)//map，映射
                .forEach(System.out::println);
    }

    /**
     * 中间操作：API
     * 3.排序
     */
    @Test
    public void test04() {
        this.list.stream()
                .sorted((u1, u2) -> u1.getAge().compareTo(u2.getAge()))
                .forEach(System.out::println);
    }

    /**
     * Stream的终止操作
     * 1. 基础操作
     * allMatch -- 检查是否匹配所有元素
     * anyMatch -- 检查是否至少匹配一个元素
     * noneMatch -- 检查是否没有匹配所有元素
     * findFirst -- 返回第一个元素
     * count -- 返回流中元素的总个数
     * max -- 返回流中最大值
     * min -- 返回流中最小值
     */
    @Test
    public void test05() {
        final boolean allMatch = this.list.stream()
                .allMatch(user -> user.getAge() > 20);
        System.out.println("是不是所有的人年龄都大于20, " + allMatch);

        final boolean anyMatch = this.list.stream()
                .anyMatch(user -> user.getAge() > 20);
        System.out.println("至少一个人年龄大于20, " + anyMatch);

        final boolean noneMatch = this.list.stream()
                .noneMatch(user -> user.getAge() > 20);
        System.out.println("没有一个人年龄大于20, " + noneMatch);

        final Optional<User> findFirst = this.list.stream().findFirst();
        System.out.println("第一个元素" + findFirst.get());

        final long count = this.list.stream().count();
        System.out.println("元素个数：" + count);

        final Optional<User> max = this.list.stream().max((u1, u2) -> u1.getAge().compareTo(u2.getAge()));
        System.out.println("年龄最大的：" + max.get());

        final Optional<User> findAny = this.list.stream().findAny();//每次都是同一个元素，用处不大
        System.out.println("获取任一元素：" + findAny.get());
    }

    /**
     * 终止操作：2.归约 reduce
     * map和reduce的连接通常称为map-reduce模式，因google用它进行网络搜索而出名
     */
    @Test
    public void test06() {
        //直接使用reduce
        final List<Integer> list1 = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
        final Integer reduce = list1.stream().reduce(0, (x, y) -> x + y);
        System.out.println(reduce);

        final Optional<Integer> reduce2 = this.list.stream()
                .map(User::getAge)//1.使用map获得对象中的年龄
                .reduce(Integer::sum);//2.将获得的年龄进行加和,Integer,Double等封装类型，在1.8添加了基本数据运算的方法
        System.out.println(reduce2.get());

    }

    /**
     * 终止操作：3.收集Collect
     * 将流转换成其他格式，接受一个Collector接口的实现，用于给Stream中元素做汇总的操作。
     * Collector接口中方法的实现决定了如何对流进行收集操作（如收集到List、Set、Map）中，
     * Collectors实用类提供了很多静态方法，可以方便的创建常用收集器实例
     */
    @Test
    public void test07() {
        // 收集名字转为list
        final List<String> collect = this.list.stream()
                .map(User::getName)
                .collect(Collectors.toList());
        collect.forEach(System.out::println);

        System.out.println("########");

        // 收集年龄转为set
        final Set<Integer> collect2 = this.list.stream()
                .map(User::getAge)
                .collect(Collectors.toSet());
        collect2.forEach(System.out::println);

        System.out.println("########");

        // 收集名字转为指定的数据结构
        final LinkedHashSet<String> collect3 = this.list.stream()
                .map(User::getName)
                .collect(Collectors.toCollection(LinkedHashSet::new));
        collect3.forEach(System.out::println);

        final Map<String, Integer> collect4 = this.list.stream()
                .collect(Collectors.toMap(User::getName, User::getAge));
        collect4.forEach((k, v) -> {
            System.out.println(k + "=" + v);
        });
    }

    @Test
    public void test08() {
        //总数
        final Long collect5 = this.list.stream()
                .collect(Collectors.counting());
        System.out.println(collect5);
        System.out.println("---------------------------------");

        //平均
        final Double collect6 = this.list.stream().collect(Collectors.averagingDouble(User::getAge));
        System.out.println(collect6);
        System.out.println("-------------------------------");

        //总和
        final Double collect7 = this.list.stream().collect(Collectors.summingDouble(User::getAge));
        System.out.println(collect7);
        System.out.println("-----------------------");

        //最大值
        // 方式1
        final Optional<User> collect8 = this.list.stream()
                .collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getAge(), e2.getAge())));
        System.out.println(collect8);
        System.out.println("-----------------------");

        // 方式2
        final Optional<User> max = this.list.stream().
                collect(Collectors.maxBy(Comparator.comparing(User::getAge)));
        max.ifPresent(System.out::println);

        final Optional<User> minDish = this.list.stream().
                collect(Collectors.minBy(Comparator.comparing(User::getAge)));
        minDish.ifPresent(System.out::println);

    }

    @Test
    public void test09() {
        //单级分组
        final Map<Integer, List<User>> collect = this.list.stream().collect(Collectors.groupingBy(User::getAge));
        System.out.println(collect);
        //{50=[User [id=5, name=DDD, age=50]], 20=[User [id=2, name=AAA, age=20]], 40=[User [id=4, name=BBB, age=40], User [id=6, name=CCC, age=40]], 10=[User [id=1, name=FFF, age=10]], 30=[User [id=3, name=EEE, age=30]]}
        System.out.println("----------------------");

        //多级分组
        final Map<Integer, Map<String, List<User>>> collect1 = this.list.stream()
                .collect(Collectors.groupingBy(User::getAge, Collectors.groupingBy(User::getName)));
        System.out.println(collect1);
        //{50={DDD=[User [id=5, name=DDD, age=50]]}, 20={AAA=[User [id=2, name=AAA, age=20]]}, 40={CCC=[User [id=6, name=CCC, age=40]], BBB=[User [id=4, name=BBB, age=40]]}, 10={FFF=[User [id=1, name=FFF, age=10]]}, 30={EEE=[User [id=3, name=EEE, age=30]]}}
        System.out.println("----------------------");

        //分区--满足条件一个区，不满足另一个区
        final Map<Boolean, List<User>> collect2 = this.list.stream()
                .collect(Collectors.partitioningBy(e -> e.getAge() > 20));
        System.out.println(collect2);
        //{false=[User [id=2, name=AAA, age=20], User [id=1, name=FFF, age=10]], true=[User [id=4, name=BBB, age=40], User [id=6, name=CCC, age=40], User [id=5, name=DDD, age=50], User [id=3, name=EEE, age=30]]}
        System.out.println("-----------------------");

        //收集各种统计数据
        final DoubleSummaryStatistics collect3 = this.list.stream().collect(Collectors.summarizingDouble(User::getAge));
        //DoubleSummaryStatistics{count=6, sum=190.000000, min=10.000000, average=31.666667, max=50.000000}-----------平均薪水31.666666666666668
        System.out.println(collect3 + "-----------平均薪水" + collect3.getAverage());

        //连接字符串
        final String collect4 = this.list.stream().map(User::getName)
                .collect(Collectors.joining(",", "-----", "-----"));
        //-----AAA,BBB,CCC,DDD,EEE,FFF-----
        System.out.println(collect4);
    }

    @Test
    public void test10() {
		/*
			List -> Map
			需要注意的是：
			toMap 如果集合对象有重复的key，会报错Duplicate key ....
			apple1,apple12的id都为1。
			可以用 (k1,k2)->k1 来设置，如果有重复的key,则保留key1,舍弃key2
		*/
        //Map<Integer, Apple> appleMap = appleList.stream().collect(Collectors.toMap(Apple::getId, a -> a,(k1,k2)->k1));
    }

    @Test
    public void test11(){
        int[] arr = new int[2];
        arr[0] = 0;
        arr[1] = 1;
        // 基本类型 数组 转 封装类型，使用boxed
        List<Integer> list = Arrays.stream(arr).boxed().collect(Collectors.toList());
        System.out.println(list);
    }
}
